#!/bin/bash -e

zm_curdir=$PWD
zm_path=$(readlink -f $0)
zm_workdir=${zm_path%/*}

SUDO=$(test "$(id -u)" = "0" || which sudo || true)

zm_version=0.05
zm_release=1


set -e 
set -E 

# cd $zm_workdir

sfs_mpath=/media/sfs
union_mpath=/media/union
sfs_part_mpath=/media/sfsroot
sfs_root_mpath=$sfs_mpath/root
sfs_home_mpath=$sfs_mpath/home
unionfs_max_branch=8
unionfs_clean_file=.unionfs.fs.clean

work_mountdir=/work
cache_dir=$work_mountdir/cache
backup_mountdir=/media/backup
backup_workdir=/media/bak

archlinux_aur_softs=""


log_file=/tmp/.zm.log
> $log_file
chmod 666 $log_file

err()
{   
    echo "Error: $@"
    exit 1
}

show_version ()
{   
    echo "$zm_version"
    exit 0
}

show_os_name() 
{
    local rootdir="$1"
    local osname=""
    if [ -e $rootdir/etc/apt/sources.list ];then
        local deb_ver=stable
        deb_ver=$(cat $rootdir/etc/apt/sources.list | grep deb | awk '{print $3}' | head -1)
        if [ "$deb_ver" != "stable" -a "$deb_ver" != "unstable" -a "$deb_ver" != "testing" ];then
            deb_ver="stable"
        fi
        osname="debian.$deb_ver"
    elif [ -e $rootdir/etc/arch-release ];then
        osname="archlinux"
    fi
    echo $osname
}

log_info()
{
    echo $@ >> $log_file
}

backup_file()
{
    local time_dir=$(date +%y%m%d)
    # local time_dir=$(date +%y%m%d_%H%M)
    while [ $# -gt 0 ]; do
        bak_file=$1
        shift
        if [ -e "$bak_file" ];then
            bak_file_dir=$(dirname $bak_file)/old/$time_dir
            $SUDO mkdir -p $bak_file_dir
            $SUDO mv -fv ${bak_file} ${bak_file_dir}/
        fi
    done
}

get_linux_kernel_code()  
{  
    #expr $(VERSION) \* 65536 + 0$(PATCHLEVEL) \* 256 + 0$(SUBLEVEL));  
    VERSION=`echo $1 | awk  -F '.' 'BEGIN{OFS="."}{print $1}'`  
    PATCHLEVEL=`echo $1 | awk  -F '.' 'BEGIN{OFS="."}{print $2}'`  
    SUBLEVEL=`echo $1 | awk  -F '.' 'BEGIN{OFS="."}{print $3}'`  
    #echo $VERSION  
    #echo $PATCHLEVEL  
    #echo $SUBLEVEL  
    KERNEL_CODE=`expr $VERSION \* 65536 + 0$PATCHLEVEL \* 256 + 0$SUBLEVEL` 
    echo $KERNEL_CODE  
}

download_file()
{
    src=$1
    dst=$2
    md5=$3
    DL="wget -c"

    if [ -e "$dst" -a -n "$md5" ];then
        echo "$md5 $dst" | md5sum -c 
        if [ $? = 0 ];then
            return
        fi
    fi

    if false;then
        if which axel;then
            if [ ! -e "$2" ];then
                axel $1 -o $2
            fi
        else
            wget -c $1 -O $2
        fi
    else
        wget -c -O $2 $1 
    fi

    if [ -n "$md5" ];then
        echo "$md5 $dst" | md5sum -c 
    fi
}

select_yesno()
{
    local info="$1"
    local title="$2"

    if [ "$zm_auto_select" = "yes" ];then
        return 0
    fi

    if [ -z "$title" ];then
        cmd="dialog --stdout --yesno \"$info\" 0 0"
    else
        cmd="dialog --stdout --title \"$title\" --yesno \"$info\" 0 0"
    fi

    if eval $cmd;then
        return 0
    else
        return 1
    fi
}

dialog_continue()
{
    if ! select_yesno "$@";then
        if $zm_debug;then
            echo "$@"
        fi
        err "Select no and exit now."
    fi
}

dialog_checklist()
{
    local win_title=$1
    local list_title=$2

    if [ -z "$win_title" -o -z "$list_title" ];then
        return 1
    fi

    shift 2
    cmd="dialog --title \"$win_title\" --stdout --checklist \"$listtitle\" 0 0 0 $@"
    if ret=$(eval $cmd);then
        echo $ret
    else
        echo ""
    fi
}

get_free_mem_size_m()
{
    free -m | grep Mem | awk '{print $4}'
}

need_root()
{
    if [ "$(id -u)" != "0" ]; then
        err "This function must be run as root" 1>&2
    fi
}

_add_samba_user()
{
    user=$1
    passwd=$2
    echo -e "$passwd\n$passwd" | $SUDO smbpasswd -a $user -s
}


usage ()
{
    local -a options
    local -a msgs
    index=0

    add_help()
    {
        options[$index]="$1"
        msgs[$index]="$2"
        index=$(expr $index + 1)
    }

    add_help "--create-part" "create part with lvm"
    add_help "--install-grub device" "install grub on device, device like /dev/sda1"
    add_help "--update-grub-config device" "update grub on device, device like /dev/sda1"
    add_help "--install-system device" "install system on device, device like /dev/sda1"
    add_help "--arch arch" "system arch, value: i386|amd64"
    add_help "--os-name" "os name, value: debian.stable|debian.testing|debian.unstable|archlinux"
    add_help "--gen-initrd [output_dir]" "generate initrd.img"
    add_help "--gen-iso-system iso_file_path" "install system to iso file"
    add_help "--build-dir dir" "build temp dir"
    add_help "--install-zm dir" "install zm to dir"
    add_help "-k, --keep-build" "Keep the temporary directory used to make the image."
    add_help "--chroot chroot_options" "like chroot options."
    add_help "--install-softs" "install softs."
    add_help "--download-softs" "download all softs."
    add_help "-m, --make-system [output dir]" "make system"
    add_help "--setup" "setup system"
    add_help "--kernel-name" "kernel name"
    add_help "--kernel-ver" "kernel ver"
    add_help "--zm-dir" "zm system dir"
    add_help "--zm-user" "zm system user's name"
    add_help "--zm-userdir" "zm system user's config dir"
    add_help "--zm-user-passwd" "zm user's password"
    add_help "--zm-root-passwd" "zm root's password"
    add_help "--zm-user-init" "run zm user init scripts, ~/.zm/init"
    add_help "--backup-dir dir [name]" "backup dir to sfs"
    add_help "--backup-branch name" "backup dir branch"
    add_help "--remove-backup-branch name 0-$unionfs_max_branch" "remove backup branch"
    add_help "--mount-backup name" "mount backup"
    add_help "--remove-backup name" "remove backup"
    add_help "--umount-backup name" "umount backup"
    add_help "--syncdir-backup name dir" "sync backup to directory"
    add_help "--backup-info name" "print backup sfs info"
    add_help "-i,--install-soft name" "install soft"
    add_help "--install-softs" "install all softs in ${os_name}.config.sh"
    add_help "--make-virtual-system" "install system to virtual image file"

    add_help "--debug" "print debug infomation"

    add_help "--print-workdir" "print workdir"
    add_help "--print-backup-workdir" "print backup workdir"
    add_help "--print-backup-mountdir" "print backup mountdir"
    add_help "--print-os-name" "print os name"
    add_help "--version" "print version"


    local maxlen=0
    for pos in $(seq 0 $(expr $index - 1))
    do
        if [ $maxlen -lt ${#options[pos]} ];then
            maxlen=${#options[pos]}
        fi
    done
    maxlen=$(expr $maxlen + 4)

    echo "Usage:  <options>"
    for pos in $(seq 0 $(expr $index - 1))
    do
        option="${options[pos]}"
        msg="${msgs[pos]}"
        if [ ${#option} -gt ${maxlen} ];then
            printf "  %-${maxlen}s  \n" "$option"
            printf "  %-${maxlen}s : %-s\n" "" "$msg"
        else
            printf "  %-${maxlen}s : %-s\n" "$option" "$msg"
        fi
    done

    exit 0
}

debug_var()
{
    local var=$1
    eval value='$'$var
    printf "  %-15s : %-s\n" $var $value | tee -a $log_file
}

print_line()
{
    local num=80
    output_char='-'
    output_str=$(yes $output_char | head -$num | tr -d '\n')
    echo "$output_str"
}

print_env()
{
    clear
    print_line
    debug_var os_name
    debug_var kernel_name
    debug_var kernel_ver
    debug_var kernel_params 
    debug_var zm_arch
    debug_var zm_dir
    debug_var zm_user
    debug_var zm_user_dir
    debug_var install_dir
    debug_var src_url
    debug_var build_dir
    for var in $@
    do
        debug_var $var
    done
    print_line
}

get_dir_res_space()
{
    # df -B1 $1 | awk '{print $4}' | /bin/grep '[[:digit:]]'
    false
}

get_freesize_dir_mb()
{
    # size_mb=$(df -mP $1 | sed -n '2p' | awk '{print $4}')
    # echo ${size_mb}
    false
}

get_usedsize_dir_mb()
{
    size_mb=$(du -ms $1 | awk '{print $1}')
    echo ${size_mb}
}

get_file_size()
{
    echo $(stat -c '%s' $1)
}

check_file_exist()
{
    local "file=$1"
    test -z "$file" && err "Please input check_file_exist() param"
    test -e "$file" || err "Cann't find file $file."
}

check_dir_exist()
{
    local "dir=$1"
    test -z "$dir" && err "Please input check_dir_exist() param"
    test -e "$dir" || err "Cann't find dir $dir."
}

check_binary()
{
    if ! which $1 > /dev/null;then
        err "Not have $1."
    fi
}

rm_safe()
{
    dir=$1

    if [ ! -d "$dir" ];then
        return
    fi

    if [ $(get_usedsize_dir_mb "$dir") -gt 100 ];then
        dialog_continue "Directory($dir) have more data, please check again."
    fi
    $SUDO rm -rf "$dir"
}

check_removeable_device()
{
    device=$1
    check_file_exist $device

    USBKEYS=($(
    grep -Hv ^0$ /sys/block/*/removable |
    sed s/removable:.*$/device\\/uevent/ |
    xargs grep -H ^DRIVER=sd |
    sed s/device.uevent.*$/size/ |
    xargs grep -Hv ^0$ |
    cut -d / -f 4
    ))
    echo $USBKEYS | grep -w $(basename $device) || err "$device is not remove disk"
}

print_disk_sizeinfo()
{
    if [ $# -gt 0 ];then
        fdisk -l $1 2>&1 | grep '^Disk /dev/.*:' | awk '{print $3$4}'
    fi
}

is_mounted()
{
    test -z "$1" && err "$FUNCNAME param error."
    # if which findmnt > /dev/null;then
        # findmnt -no source "$1" > /dev/null
    # fi
    if cat /proc/mounts | grep " $1 " > /dev/null 2>&1;then
        return 0
    fi
    return 1
}

is_installed()
{
    case "$os_name" in
        debian.*)
            if dpkg-query -f '${db:Status-Status}\n' -W $@ 2>&1 | grep -v ^installed > /dev/null 2>&1;then
		return 1
	    else
		return 0
	    fi
            ;;
        archlinux)
            pacman -Qs $@ || return 1
            ;;
        *)
            ;;
    esac
}

mount_device()
{
    last_param=${!#}
    if [ $# -gt 1 ];then
        $SUDO mount $@
    fi

    if [ ! -e $build_dir/.mount_devs ];then
        touch $build_dir/.mount_devs
    fi

    if grep "$last_param" $build_dir/.mount_devs > /dev/null;then
        echo "$last_param have mounted."
        return
    fi

    tmpfile=$build_dir/.mount_devs.temp
    (echo "$last_param" | cat - $build_dir/.mount_devs > $tmpfile) && mv $tmpfile $build_dir/.mount_devs
}

umount_device()
{
    if [ -e $build_dir/.mount_devs ];then
        $SUDO sh -c "cat /proc/mounts | grep ' $1 ' > /dev/null && umount $1 && sed -i \":^$1$:d\" $build_dir/.mount_devs || true"
    fi
}

sfs2dev()
{
    filename=$1
    device=$2
    restore_dir=$3

    echo -n "[ $FUNCNAME ] $filename -> $device "
    check_file_exist $filename
    check_file_exist $device

    mount_device $device $mnt_dir

    if [ ! -d $mnt_dir/$restore_dir ]; then 
        mkdir -p $mnt_dir/$restore_dir -m 0755
    fi
    sfs2dir $filename $mnt_dir/$restore_dir

    umount_device $mnt_dir
}

get_all_sfs()
{
    local sfsfile=$1
    if [ -e "$sfsfile" ];then
        echo ${sfsfile}
        for branch in $(seq 1 $unionfs_max_branch)
        do
            if [ -e "${sfsfile}.${branch}" ];then
                echo ${sfsfile}.${branch}
            fi
        done
    fi
}

umount_all_sfs()
{
    local sfs_mnt_dir="$1"
    local sfs_name="$2"

    for branch in $(seq 1 $unionfs_max_branch)
    do
        branch_sfs_mnt_dir=${sfs_mnt_dir}/${sfs_name}.${branch}
        if [ -d "$branch_sfs_mnt_dir" ];then
            $SUDO umount $branch_sfs_mnt_dir
            continue
        fi
        break
    done  
    $SUDO umount $sfs_mnt_dir/$sfs_name
}

update_label()
{
    device=$1
    label=$2
    real_label=`e2label $device`
    if [ '$real_label' != '$label' ];then
        e2label $device $label
    fi
}

boot_log()
{
    debug_file=/run/initramfs/initramfs-debug
    if [ -e "$debug_file" ];then
        $EDITOR $debug_file
    fi
}

defalut_install_dir()
{
    install_dir=$1
    if [ -z "$install_dir" -a $# = 2 ];then
        install_dir="$2"
    fi
    if [ -z "$install_dir" ];then
        install_dir=$zm_dir
        if [ ! -d $zm_dir ];then
            mkdir -p ${sfs_part_mpath}
            mount_device LABEL=sfsroot ${sfs_part_mpath}
        fi
    fi
    check_file_exist $install_dir
}

zm_copy_source()
{
    local _dst_dir=$1

    $SUDO install -D $zm_workdir/zm $_dst_dir/zm
    $SUDO $CP $zm_workdir/{common,user,boot,Makefile,test} $_dst_dir/

    # git archive --format=tar.gz --prefix=zm/ HEAD > $1
}

install_grub_on_dir()
{
    local installdir=$1
    check_file_exist $installdir
    check_file_exist /usr/lib/grub/i386-pc

    $SUDO mkdir -p $installdir/boot/grub
    $SUDO $CP /usr/lib/grub/i386-pc $installdir/boot/grub/
    $SUDO $CP $zm_workdir/boot/grub/* $installdir/boot/grub/
}

install_grub()
{
    local -A items
    items['disk']="/dev/sda"
    items['root']=$(mount | grep sfsroot | awk '{print $1}')
    items['modules']="biosdisk,part_msdos,lvm"

    OLD_IFS=$IFS

    local -a params
    IFS=:; params=($@); IFS=$OLD_IFS
    for opt in ${params[*]}
    do
        item=${opt/=*}
        value=${opt#*=}
        # echo $item = $value
        items["$item"]="$value"
    done

    local disk=$(readlink -f ${items['disk']})
    local root="${items['root']}"
    local -a modules
    IFS=,; modules=(${items['modules']}); IFS=$OLD_IFS

    check_file_exist $disk
    log_info "install grub on $disk"
    dialog_continue "Install grub on $disk, root=$root"

    local rootpart=""
    if [ -b "$root" ];then
        rootpart=$root
        root=$mnt_dir
        mount_device $rootpart $root
    fi
    install_grub_on_dir $root
    # $SUDO grub-mkdevicemap -m $root/boot/grub/device.map
    $SUDO grub-install $disk --no-floppy --boot-directory=$root/boot --modules="${modules[*]}"
    if [ -b "$rootpart" ];then
        umount_device $root
    fi

    #恢复主扇区
    #  /sbin/install-mbr $(echo ${disk} | tr -d [0-9])
    #激活扇区
    # sfdisk -A11 ${disk}
    # local disk_device=${disk//[0-9]*}
}

init_lvm_partition()
{
    local -A items
    items['name']='zm'
    items['parts']=''
    items['sfsroot']='20G'
    items['home']='20G'
    items['union']='20G'
    items['swap']='4G'
    items['backup']='50%VG'
    items['work']='100%FREE'

    OLD_IFS=$IFS

    local -a params
    IFS=:; params=($@); IFS=$OLD_IFS
    for opt in ${params[*]}
    do
        item=${opt/=*}
        value=${opt#*=}
        # echo $item = $value
        items["$item"]="$value"
    done
    # declare -p items
    for k in "${!items[@]}"; do echo "$k = ${items[$k]}"; done

    local -a parts
    IFS=,; parts=(${items['parts']}); IFS=$OLD_IFS
    for part in ${parts[*]}
    do
        test -b $part || err "no partition $part"
        if [ "${part:0:8}" = "/dev/nbd" ];then
            continue
        fi

        local num=$(echo $part | sed -re 's/[^0-9]*([0-9]*).*$/\1/;')
        local disk=$(echo $part | sed -re 's/([^0-9]*)[0-9]*$/\1/;')
        if [ -n "$num" ];then
            $SUDO parted $disk set $num lvm on
        fi
    done

    dialog_continue "create lvm part on ${parts[*]}."

    local vgname=${items['name']}
    $SUDO pvcreate ${parts[*]}
    $SUDO vgcreate $vgname ${parts[*]}

    create_part()
    {
        local name=$1
        local size=$2 

        if [ -z "$size" ];then
            return 0
        fi

        if [ "$size" = '-' ];then
            size='100%FREE'
        fi

        if expr index "$size" '%' > /dev/null;then
            $SUDO lvcreate -l $size -n ${name} $vgname
        else
            $SUDO lvcreate -L $size -n ${name} $vgname
        fi

        if [ "$name" = "swap" ];then
            $SUDO mkswap -f -L ${vgname}-swap /dev/mapper/${vgname}-${name}
        else
            $SUDO mkfs.ext4 -L ${vgname}-${name} /dev/mapper/${vgname}-${name}
        fi
    }
    create_part sfsroot ${items['sfsroot']}
    create_part home ${items['home']}
    create_part union ${items['union']}
    create_part swap ${items['swap']}
    create_part backup ${items['backup']}
    create_part work ${items['work']}
}

format_usb_device()
{
    usb_device=$1
    check_removeable_device $usb_device

    umount "$usb_device"* > /dev/null 2>&1 || true
    umount "$usb_device"* > /dev/null 2>&1 || true
    umount "$usb_device"* > /dev/null 2>&1 || true
    if is_mounted $usb_device;then 
        err "$usb_device is mounted!"
    fi

    total_size=` fdisk -l $usb_device | grep Disk | awk '{print $5}'`
    total_cyln=`echo $total_size/255/63/512 | bc`

    diskinfo=` sfdisk -H 255 -S 63 -C $total_cyln -l $usb_device | grep Disk`
    cylns=`echo $diskinfo | awk '{print $3}'`
    heads=`echo $diskinfo | awk '{print $5}'`
    sectors=`echo $diskinfo | awk '{print $7}'`
    disksize=`echo "$cylns*$heads*$sectors*512" | bc`
    echo disksize = $disksize
    ext2_size_bytes=2500000000
    fat_cyln=`echo "($disksize-$ext2_size_bytes)/$heads/$sectors/512" | bc`
    fat_sectors=`echo "$fat_cyln*$heads*$sectors" | bc`
    echo fat_cyln = $fat_cyln
    echo fat_sectors = $fat_sectors

    echo -n " $usb_device size is "
    print_disk_sizeinfo $usb_device
    echo "Format $usb_device now?"
    dialog_continue now

    dd if=/dev/zero of=$usb_device bs=512 count=63
    sync;sleep 1;
    blockdev --rereadpt $usb_device

    sfdisk --label dos $usb_device -H 255 -S 63 -C $total_cyln << EOF
,$fat_cyln,0xc,*
,,0x83,-
EOF
    sync;sync;sync

    sleep 3;
    umount "$usb_device"* > /dev/null 2>&1 || true
    umount "$usb_device"* > /dev/null 2>&1 || true
    umount "$usb_device"* > /dev/null 2>&1 || true
    if is_mounted $usb_device;then 
        err "$usb_device is mounted!"
    fi

    blockdev --rereadpt $usb_device
    #  sfdisk -l $usb_device
    part1_dev=${usb_device}1
    part2_dev=${usb_device}2
    test -b $part1_dev || err "no part $part1_dev"
    test -b $part2_dev || err "no part $part2_dev"
    dd if=/dev/zero of=$part1_dev bs=512 count=1
    dd if=/dev/zero of=$part2_dev bs=512 count=1

    mkfs.vfat -n udisk $part1_dev
    mkfs.ext4 -L sfsroot_usb $part2_dev
    sync;sync;sync

    sleep 3;
    umount "$usb_device"* > /dev/null 2>&1 || true
    umount "$usb_device"* > /dev/null 2>&1 || true
    umount "$usb_device"* > /dev/null 2>&1 || true
    if is_mounted $usb_device;then 
        err "$usb_device is mounted!"
    fi
}

gen_iso_file()
{
    check_binary genisoimage

    iso_label="sfs_iso"
    iso_filename=$1

    install_grub_on_dir $iso_dir
    local temp_path=/linux/$zm_arch/${zm_user}
    local os_class=${os_name/.*}
    echo "
    menuentry \"ZM($zm_arch) (default)\" --class ${os_class} {
    set gfxpayload=keep
    search --no-floppy -l --set=root $iso_label
    echo 'Loading kernel...'
    linux $temp_path/$kernel_name sfs_part=cdrom zm_user=$zm_user zm_arch=$zm_arch
    echo 'Loading initrd...'
    initrd $temp_path/$initrd_name
    savedefault
}
" > $iso_dir/boot/grub/grub.cfg

#生成iso grub 引导file
pushd .
cd $iso_dir/boot/grub/i386-pc
bootfile=g2hdr
# grub-mkimage -O i386-pc -o core.img biosdisk ext2 fat iso9660
grub-mkimage -O i386-pc -o core.img biosdisk iso9660
cat /usr/lib/grub/i386-pc/cdboot.img core.img > ../$bootfile
popd

rm -f $iso_filename
# “-b”后的文件路径不能是绝对路径，也不能是相对当前目录的路径，只能是相对于ISO源内容目录的路径。
genisoimage -V $iso_label -R -J -no-emul-boot -boot-load-size 4 -boot-info-table -b boot/grub/$bootfile -o $iso_filename $iso_dir
}

mount_storage()
{
    mount_src=$1
    mount_point=$2
    $SUDO mkdir -p $mount_point
    $SUDO mount $mount_src $mount_point || err "mount $@ error"
}

umount_storage()
{
    mount_point=$1
    if [ -e "$mount_point" ];then
        $SUDO umount $mount_point || err "umount $@ error"
        if [ -d "$mount_point" ];then
            $SUDO rmdir $mount_point
        fi
    fi
}

__set_lowerdir()
{
    local new=$1
    local orig=$2
    if [ -n "$orig" ];then
        echo ${new}:${orig}
    else
        echo $new
    fi
}

__mount_with_aufs()
{
    local sfsname="$1"
    local sfsdir="$2"
    local sfsmountdir=$3
    local upperdir="$4"
    local mountdir="$5"

    test -d "$upperdir" || err "upperdir: $upperdir is not found!"
    test -d "$mountdir" || err "mountdir: $mountdir is not found!"

    upperdir=$upperdir/aufs
    $SUDO mkdir -p $upperdir

    local sfsfile=$(readlink -f "${sfsdir}/${sfsname}.sfs")
    test -f "$sfsfile" || err "sfsfile: ${sfsdir}/${sfsname}.sfs is not found!"

    if [ -e "${upperdir}/$unionfs_clean_file" ];then
        $SUDO rm -rf ${upperdir}/{*,.[!.]*,..?*}
    fi

    local lowerdir=""
    local sfsmpath=$sfsmountdir/$sfsname

    mount_storage $sfsfile $sfsmpath
    lowerdir=$(__set_lowerdir ${sfsmpath}=rr $lowerdir)

    for branch in $(seq 1 $unionfs_max_branch)
    do
        if [ -e "${sfsfile}.${branch}" ];then
            mount_storage ${sfsfile}.${branch} ${sfsmpath}.${branch}
            lowerdir=$(__set_lowerdir ${sfsmpath}.${branch}=ro+wh $lowerdir)
            # $SUDO mount -t aufs -o remount,udba=none,add:1:${sfsmpath}.${branch}=ro+wh none $mountdir
            continue
        fi
        break
    done

    # $SUDO mount -t aufs -o br:$upperdir none $mountdir
    # $SUDO mount -t aufs -o remount,udba=none,append:${sfsmpath}=ro none $mountdir

    log_info "$FUNCNAME : lowerdir = $lowerdir"
    $SUDO mount -t aufs -o udba=none,br:$upperdir:$lowerdir none $mountdir
}

__mount_with_overlay()
{
    local sfsname="$1"
    local sfsdir="$2"
    local sfsmountdir=$3
    local upperdir="$4"
    local mountdir="$5"

    test -d "$upperdir" || err "upperdir: $upperdir is not found!"
    test -d "$mountdir" || err "mountdir: $mountdir is not found!"

    local workdir=$upperdir/work
    upperdir=$upperdir/upper
    $SUDO mkdir -p $workdir
    $SUDO mkdir -p $upperdir

    local sfsfile=$(readlink -f "${sfsdir}/${sfsname}.sfs")
    test -f "$sfsfile" || err "sfsfile: ${sfsdir}/${sfsname}.sfs is not found!"

    if [ -e "${upperdir}/$unionfs_clean_file" ];then
        $SUDO rm -rf ${upperdir}/{*,.[!.]*,..?*}
    fi

    local lowerdir=""
    local sfsmpath=$sfsmountdir/$sfsname

    mount_storage $sfsfile $sfsmpath
    lowerdir=$(__set_lowerdir ${sfsmpath} $lowerdir)

    for branch in $(seq 1 $unionfs_max_branch)
    do
        if [ -e "${sfsfile}.${branch}" ];then
            mount_storage ${sfsfile}.${branch} ${sfsmpath}.${branch}
            lowerdir=$(__set_lowerdir ${sfsmpath}.${branch} $lowerdir)
            continue
        fi
        break
    done

    # debug_var lowerdir
    options="lowerdir=$lowerdir,upperdir=$upperdir,workdir=$workdir"
    $SUDO mount -t overlay -o $options overlay $mountdir
}

support_union_fs()
{
    if lsmod | grep overlay > /dev/null 2>&1;then
        echo "overlay"
        return
    fi

    if lsmod | grep aufs > /dev/null 2>&1;then
        echo "aufs"
        return
    fi

}

mount_unionfs()
{
    # sfsname sfsdir sfsmountdir upperdir mountdir

    case $(support_union_fs) in
        overlay)
            __mount_with_overlay $@
            ;;
        aufs)
            __mount_with_aufs $@
            ;;
        *)
            err "no support union filesystem."
            ;;
    esac
}

zm_build_deb_checkinstall() 
{
    which checkinstall > /dev/null 
    $SUDO checkinstall --pkgname zm --pkgversion $zm_version --pkgarch all \
        --pkgrelease "${zm_release}" --pkglicense GPL --maintainer 51feel@gmail.com \
        --requires "checkinstall,debootstrap,genisoimage,mbr,squashfs-tools,dialog,syslinux" \
        -y --nodoc --deldesc=yes --delspec=yes --deldoc=yes --backup=no
    $SUDO dpkg -r zm
    $SUDO dpkg -i zm_${zm_version}-${zm_release}_all.deb
}

zm_build_deb() 
{
    local _deb_dir=$build_dir/deb
    local _install_dir=$build_dir/zm
    local _tar_file=$_deb_dir/zm.tar.gz

    # zm_git_url=

    mkdir -p $_deb_dir
    mkdir -p $_install_dir

    install_zm $_install_dir

    zm_copy_source $_install_dir
    tar czf ${_tar_file} -C $_install_dir . --exclude=.git

    export EMAIL='51feel@gmail.com'
    export DEBEMAIL='51feel@gmail.com'
    export DEBFULLNAME='Zeroman Yang'

    cd $_deb_dir
    dh_make -y -m --copyright=gpl -p zm_${zm_version} --file ${_tar_file} || true
    dpkg-buildpackage -d

}

get_sfs_cur_branch_num()
{
    local sfsfile=$1

    if [ ! -e "${sfsfile}" ];then
        err "${sfsfile} is not exist."
    fi

    local branch=0
    local cur_branch=0
    for branch in $(seq 1 $unionfs_max_branch)
    do
        if [ ! -e ${sfsfile}.${branch} ];then
            break
        fi
        cur_branch=$branch
    done

    echo $cur_branch
}

get_sfs_new_branch_num()
{
    local branch=$(get_sfs_cur_branch_num $1)

    if [ "$branch" = "$unionfs_max_branch" ];then
        err "$sfsfile have max branch."
    fi
    echo $(expr $branch + 1)
}

zm_chroot()
{
    param1="$1"
    shift

    extension=${param1##*.}
    if [ "$extension" = "sfs" ];then
        sfsname=$(basename $param1)
        name=${sfsname//.*}
        sfsdir=$(dirname $param1)
        mount_device -t tmpfs -o mode=755 tmpfs $unionfs_dir
        # sfsname sfsdir sfsmountdir upperdir mountdir
        mount_unionfs $name $sfsdir $sfs_mnt_dir $unionfs_dir $root_dir
        mount_device $sfs_mnt_dir/$name
        mount_device $root_dir
        chroot_dir=$root_dir
    else
        chroot_dir=$(readlink -f "$param1")
    fi

    os_name=$(show_os_name $chroot_dir)
    zm_dir=$sfs_part_mpath/linux/${zm_user}.${os_name}.${zm_arch}

    local chroot_cmd="$SUDO systemd-nspawn -M $zm_user -bD $chroot_dir"
    if [ -d "$cache_dir" ];then
        chroot_cmd+=" --bind $cache_dir"
    fi
    if [ -d "$zm_dir" ];then
        chroot_cmd+=" --bind $zm_dir"
    fi
    if which systemd-nspawn > /dev/null;then
        local cmd="$*"
        local requires="basic.target"
        if [ -z "$cmd" ];then
            cmd='/bin/bash'
        fi
        # $SUDO sed -i "s#ExecStart=.*#ExecStart=-/bin/sh -c \"$*;poweroff\"#g" 
        echo "
[Unit]
Description=Auto install service 
After=$requires
Requires=$requires

[Service]
ExecStart=-/bin/sh -c \"$cmd;systemctl poweroff\"
StandardInput=tty-force
StandardOutput=inherit
StandardError=inherit
KillMode=process
IgnoreSIGPIPE=no
SendSIGHUP=yes
" | $SUDO tee $chroot_dir/lib/systemd/system/zm.chroot.service
        $chroot_cmd systemd.unit=zm.chroot.service
        rm -f $chroot_dir/lib/systemd/system/zm.chroot.service
        return 0
    fi

    test -e $chroot_dir/bin/bash
    $CP /etc/resolv.conf $chroot_dir/etc/resolv.conf
    mount_device -t proc -o nosuid,noexec,nodev proc $chroot_dir/proc
    mount_device -t sysfs -o nosuid,noexec,nodev,ro sys $chroot_dir/sys
    mount_device -t devtmpfs -o mode=0755,nosuid udev $chroot_dir/dev
    mount_device -t devpts -o mode=0620,gid=5,nosuid,noexec devpts $chroot_dir/dev/pts
    mount_device -t tmpfs -o mode=1777,nosuid,nodev shm $chroot_dir/dev/shm
    mount_device -t tmpfs -o nosuid,nodev,mode=0755 run $chroot_dir/run
    mkdir -p $chroot_dir/run/lock
    if [ -d "$cache_dir" ];then
        mkdir -p $chroot_dir/$cache_dir
        mount_device --bind $cache_dir $chroot_dir/$cache_dir
    fi
    if [ -d "$zm_dir" ];then
        mkdir -p $root_dir/$zm_dir
        mount_device --bind $zm_dir $root_dir/$zm_dir
    fi

    echo "chroot to $chroot_dir "
    SHELL=/bin/bash unshare --fork --pid chroot $chroot_dir $@

    chroot_process=$(lsof -t $chroot_dir) || true
    if [ -n "$chroot_process" ];then
        kill -9 $chroot_process
    fi

    if [ -d "$zm_dir" ];then
        umount_device $chroot_dir/$zm_dir
    fi
    if [ -d "$cache_dir" ];then
        umount_device $chroot_dir/$cache_dir
    fi
    umount_device $chroot_dir/run
    umount_device $chroot_dir/dev/shm
    umount_device $chroot_dir/dev/pts
    umount_device $chroot_dir/dev
    umount_device $chroot_dir/sys
    umount_device $chroot_dir/proc
}

get_kernel_file()
{
    case "$os_name" in
        debian.*)
            if [ -z "$kernel_ver" ];then
                vmlinuz=$(readlink -f /vmlinuz)
            else
                vmlinuz=${kernel_name}-${kernel_ver}
            fi
            ;;
        archlinux)
            vmlinuz=$(readlink -f /boot/vmlinuz-linux)
            ;;
        *)
            vmlinuz=""
            ;;
    esac
    echo "$vmlinuz"
}

get_kernel_ver()
{
    ver=$kernel_ver
    if [ -z "$kernel_ver" ];then
        vmlinuz=$(get_kernel_file)
        ver=$(file $vmlinuz | grep 'Linux kernel.*boot executable' | sed 's/.*version \([^ ]\+\).*/\1/')
    fi
    echo $ver
}

gen_kernel()
{
    defalut_install_dir $1
    vmlinuz=$(get_kernel_file)
    backup_file $install_dir/${kernel_name}
    $SUDO $CP $vmlinuz $install_dir/${kernel_name}
}

gen_debian_initrd()
{
    defalut_install_dir $1
    kernel_ver=$(get_kernel_ver)
    backup_file $install_dir/${initrd_name}
    # $SUDO mkinitramfs -d $zm_workdir/initramfs -o $install_dir/${initrd_name} $kernel_ver
    $SUDO mkinitramfs -o $install_dir/${initrd_name} $kernel_ver
}

gen_archlinux_initrd()
{
    defalut_install_dir $1
    kernel_ver=$(get_kernel_ver)
    backup_file $install_dir/${initrd_name}
    $SUDO mkinitcpio -A sfsroot,lvm2 -S autodetect -k "$kernel_ver" -g ${install_dir}/${initrd_name} 
}

gen_initrd()
{
    echo "Genarate $install_dir/${initrd_name}..."
    case "$os_name" in
        debian.*)
            gen_debian_initrd $@
            ;;
        archlinux)
            gen_archlinux_initrd $@
            ;;
        *)
            return
            ;;
    esac
}

gen_common_sfs()
{
    local srcdir=$1
    local dstsfs=$2
    # shift 2

    check_binary mksquashfs

    temp_exclude_file=$(mktemp)
    echo "lost+found" >> $temp_exclude_file
    echo "$unionfs_clean_file" >> $temp_exclude_file
    # echo "$@" | xargs -n1 >> $temp_exclude_file

    if [ -e ${srcdir}/.zmignore ];then
        cat ${srcdir}/.zmignore >> $temp_exclude_file
    fi

    sed -i -e 's/^[ \t]*//;s/[ \t]*$//' -e '/^$/d' $temp_exclude_file

    if $zm_debug;then
        cat $temp_exclude_file
    fi

    $SUDO rm -fv ${dstsfs}.new
    $SUDO mksquashfs ${srcdir} ${dstsfs}.new -noappend -comp lz4 -regex -ef $temp_exclude_file

    backup_file ${dstsfs} ${dstsfs}.[0-9]
    $SUDO mv ${dstsfs}.new ${dstsfs}
    $SUDO touch ${srcdir}/$unionfs_clean_file

    rm -f $temp_exclude_file
}

gen_root_sfs()
{
    echo '
    sys/.* 
    proc/.* 
    run/.* 
    var/tmp/.* 
    tmp/.* 
    media/.* 
    mnt/.* 
    dev/.*
    dev/disk/.*/.* 
    etc/mtab 
    etc/fstab 
    etc/udev/rules.d/70-.*net.* 
    etc/X11/xorg.conf* 
    usr/portage/distfiles/.* 
    etc/hal/fdi/policy/11-x11-vmmouse.fdi 
    usr/share/hal/fdi/policy/20thirdparty/11-x11-vmmouse.fdi 
    usr/bin/vmmouse_detect 
    var/cache/hald/fdi-cache 
    var/cache/apt/archives/.* 
    work/.* 
    home/.*/.*
    var/log/journal
    ' | $SUDO tee $1/.zmignore
    gen_common_sfs $1 $2 
}

gen_home_sfs()
{
    echo '
    .*/.xauth*
    .*/.serverauth
    .*/Downloads/.*
    .*/.cache
    .*/.backup/.*
    .*/.xsession-errors
    .*/.kermit.log
    .*libreoffice.*' | $SUDO tee $1/.zmignore
    gen_common_sfs $1 $2 
}

update_system()
{
    local sels="$@"

    if [ -z "$sels" ];then
        local options=""
        options+='"kernel" "update kernel" "off" '
        options+='"initrd" "update initrd" "off" '
        options+='"root" "update root filesystem" "off" '
        options+='"home" "update home filesystem" "on" '
        sels=$(dialog_checklist "Update system" "Select update item:" $options)
        if [ -z "$sels" ];then
            return
        fi
    fi

    dialog_continue "[$sels] will update, continue?" "Update system"

    for sel in $sels;do
        case "$sel" in
            kernel)
                gen_kernel $zm_dir
                ;;
            initrd)
                gen_initrd $zm_dir
                ;;
            root)
                gen_root_sfs / $zm_dir/${root_sfs_name}
                ;;
            home)
                gen_home_sfs /home $zm_dir/${home_sfs_name}
                ;;
            *)
                ;;
        esac
    done
}

branch_system()
{
    local sels="$@"

    if [ -z "$sels" ];then
        local options=""
        options+='"root" "make branch on root filesystem" "on" '
        options+='"home" "make branch on home filesystem" "on" '
        sels=$(dialog_checklist "Update system" "Select update item:" $options)
        if [ -z "$sels" ];then
            return
        fi
    fi

    dialog_continue "[$sels] will be make branch, continue?" "Make branch"

    local sfsfile
    local branch

    print_env

    for sel in $sels;do
        case "$sel" in
            root)
                sfsfile=$zm_dir/${root_sfs_name}
                branch=$(get_sfs_new_branch_num $sfsfile)
                gen_root_sfs ${unionfs_root_mpath}/ ${sfsfile}.${branch}
                ;;
            home)
                sfsfile=$zm_dir/${home_sfs_name}
                branch=$(get_sfs_new_branch_num $sfsfile)
                gen_home_sfs ${unionfs_home_mpath}/ ${sfsfile}.${branch}
                ;;
            *)
                ;;
        esac
    done
}

__merge_with_aufs()
{
    for item in $@;do
        if [ -d "$item" ];then
            echo ""
        fi
        if [ -f "$item" ];then
            echo ""
        fi
    done
}

merge_branch()
{
    case $(support_union_fs) in
        overlay)
            __merge_with_overlay $@
            ;;
        aufs)
            __merge_with_aufs $@
            ;;
        *)
            err "no support union filesystem."
            ;;
    esac
}

sfs2dir()
{
    7z x $1 -o${2}
}

update_grub_config()
{
    local grubdev="$1"

    if [ -z "$grubdev" ];then
        grubdev=$(mount | grep -w "$sfs_part_mpath" | awk '{print $1}')
    fi

    local zm_name_param=$(cat /proc/cmdline | tr ' ' '\n' | grep zm_name)
    local grubroot="$(mktemp -d)"
    local boot_uuid=$(blkid -s UUID $grubdev | awk -F= '{print $2}' | tr -d '"')
    local temp_path=/linux/${zm_user}.${os_name}.${zm_arch}
    local os_class=${os_name/.*}
    local base_kernel_params="cgroup_enable=memory swapaccount=1 $zm_name_param" #for docker


    mount_device $grubdev $grubroot
    check_dir_exist "$grubroot/$temp_path"
    check_dir_exist "$grubroot/boot/grub/config"
    echo "
menuentry 'ZM linux (${zm_user}.${os_name}.${zm_arch})' --class=submenu --class ${os_class} {
    set root_uuid=$boot_uuid
    search --no-floppy --fs-uuid --set=root \$root_uuid
    set zm_dir=$temp_path
    echo 'Loading $zm_arch kernel...'
    linux \$zm_dir/${kernel_name} sfs_part=UUID=\$root_uuid $base_kernel_params $kernel_params
    echo 'Loading $zm_arch initrd...'
    initrd \$zm_dir/${initrd_name}
    savedefault
}" > $grubroot/boot/grub/config/default.cfg

    local items=""
    check_dir_exist "$grubroot/linux"
    # sed "s/\bzm\b/$zm_user/g" $zm_workdir/common/linux.cfg > $grubroot/boot/grub/config/linux.cfg
    local linuxcfg=$grubroot/boot/grub/config/linux.cfg
    echo "" | $SUDO tee $linuxcfg > /dev/null
    echo "submenu 'zm linux  ->' --class=submenu --class ${os_class} {
    search --no-floppy -l --set=root sfsroot 
    " | $SUDO tee -a $linuxcfg > /dev/null
    items=$(cd $grubroot/linux;find * -name root.sfs -exec dirname {} \; | grep old || true)
    if [ -n "$items" ];then
        echo "submenu 'zm linux (old) ->' --class=submenu --class ${os_class} {
        search --no-floppy -l --set=root sfsroot " | $SUDO tee -a $linuxcfg > /dev/null
        for dir in $items
        do
            echo "
        menuentry '${dir}' --class=submenu --class ${os_class} {
            set     zm_dir=/linux/${dir}
            linux	\$zm_dir/vmlinuz $base_kernel_params
            initrd	\$zm_dir/initrd.img
            savedefault
        } " | $SUDO tee -a $linuxcfg > /dev/null
        done
        echo "}" | $SUDO tee -a $linuxcfg > /dev/null
    fi
    items=$(cd $grubroot/linux;find * -name root.sfs -exec dirname {} \; | grep -v old || true)
    for dir in $items
    do
        echo "
    menuentry '${dir}' --class=submenu --class ${os_class} {
        set     zm_dir=/linux/${dir}
        linux	\$zm_dir/vmlinuz
        initrd	\$zm_dir/initrd.img
        savedefault
    } " | $SUDO tee -a $linuxcfg > /dev/null
    done
    echo "}" | $SUDO tee -a $linuxcfg > /dev/null

    umount_device $grubroot
    rmdir $grubroot
}

gen_iso_system()
{
    iso_file=$1
    test -z "$iso_file" && err "iso file is null"
    install_system $iso_dir
    gen_iso_file $iso_file
}

gen_usb_boot ()
{
    usb_device=$1
    check_file_exist $usb_device

    grubdev=${usb_device}2
    if [ ! -b ${grubdev} ];then
        format_usb_device $usb_device
    fi
    check_file_exist $grubdev
    install_grub $grubdev
    update_grub_config $grubdev
}

gen_usb_system()
{
    usb_device=$1
    check_file_exist $usb_device

    if [ ! -b ${usb_device}2 ];then
        format_usb_device $usb_device
    else 
        disk_id=$(sfdisk --print-id $usb_device 2)
        if [ $disk_id != 83 ];then
            format_usb_device $usb_device
        fi
    fi
    usb_root_device=${usb_device}2
    check_file_exist $usb_root_device

    install_system $usb_root_device 
}

make_lvm_virtual_system()
{
    local img_file=/tmp/lvm.qcow2
    local img_size=32G
    local nbdindex=0

    $SUDO rmmod nbd || true
    $SUDO modprobe nbd max_part=16

    init_qemu_disk $nbdindex $img_file $img_size qcow2
    init_lvm_partition name=zm:parts=/dev/nbd${nbdindex}p1:sfsroot=10G:home=:backup=2G:swap=2G:union=2G:work=-
    install_system /dev/mapper/zm-sfsroot
    install_grub disk=/dev/nbd0:root=/dev/mapper/zm-sfsroot
    update_grub_config /dev/mapper/zm-sfsroot
    $SUDO vgchange -an zm
    uninit_qemu_disk $nbdindex 
    $SUDO vgscan --cache
    $SUDO vgs
}

init_qemu_disk()
{
    local index=$1
    local img_file=$2
    local img_size=$3
    local img_format=$4

    if [ -z "$img_size" ];then
        return 0
    fi

    if [ -e "$img_file" ];then
        dialog_continue "$img_file exists, delete it?"
        rm -f $img_file
    fi

    if [ ! -e "$img_file" ];then
        qemu-img create -f $img_format "$img_file" "$img_size"
    fi
    $SUDO qemu-nbd --format=$img_format --cache=writeback -c /dev/nbd${index} $img_file
    $SUDO dd if=/dev/zero of=/dev/nbd${index} bs=1k count=64
    if [ -e /usr/lib/syslinux/mbr.bin ];then
        $SUDO dd if=/usr/lib/syslinux/mbr.bin of=/dev/nbd${index}
    fi
    $SUDO parted -s -a optimal /dev/nbd${index} mklabel msdos
    $SUDO parted -s -a optimal /dev/nbd${index} mkpart primary ext2 0% 100%

    while [ ! -e /dev/nbd${index}p1 ];do
        $SUDO blockdev --rereadpt /dev/nbd${index}
        sleep 1
    done
}

uninit_qemu_disk()
{
    local index=$1

    $SUDO blockdev --flushbufs /dev/nbd${index}p1
    $SUDO blockdev --flushbufs /dev/nbd${index}
    $SUDO qemu-nbd -d /dev/nbd${index}
}

convert_qemu_img()
{
    local informat=$1
    local outformat=$2
    local inputfile=$3
    local outputfile=$4

    if [ "$informat" != "$outformat" ];then
        qemu-img convert -f $informat -O $outformat $inputfile $outputfile
    fi
}

make_virtual_system()
{
    # check_binary install-mbr 
    check_binary syslinux

    local -A items
    items['format']='qcow2' #vdi vmdk qcow2
    items['orig_format']='qcow2' #vdi vmdk qcow2
    items['sfsroot']='20G'
    items['union']='20G'
    items['swap']='4G'
    items['backup']='128G'
    items['work']='128G'
    items['home']='128G'
    items['dir']='/tmp/zm'

    OLD_IFS=$IFS

    local -a params
    IFS=:; params=($@); IFS=$OLD_IFS
    for opt in ${params[*]}
    do
        item=${opt/=*}
        value=${opt#*=}
        # echo $item = $value
        items["$item"]="$value"
    done
 
    local disk_format=${items['format']}
    local installdir=${items['dir']}

    local sfsroot_file=$installdir/sfsroot.$disk_format
    local swap_file=$installdir/swap.$disk_format
    local work_file=$installdir/work.$disk_format
    local home_file=$installdir/home.$disk_format
    local union_file=$installdir/union.$disk_format
    local backup_file=$installdir/backup.$disk_format

    local orig_format=${items['orig_format']}
    local sfsroot_qcow2_file=$installdir/sfsroot.$orig_format
    local swap_qcow2_file=$installdir/swap.$orig_format
    local work_qcow2_file=$installdir/work.$orig_format
    local home_qcow2_file=$installdir/home.$orig_format
    local union_qcow2_file=$installdir/union.$orig_format
    local backup_qcow2_file=$installdir/backup.$orig_format

    local sfsroot_size=${items['sfsroot']}
    local swap_size=${items['swap']}
    local work_size=${items['work']}
    local home_size=${items['home']}
    local union_size=${items['union']}
    local backup_size=${items['backup']}

    mkdir -p $installdir

    local info="Create virtual disk:\n"
    info+=" install dir : $installdir\n" 
    info+=" disk format : $disk_format\n" 
    info+=" sfsroot     : $sfsroot_size\n" 
    info+=" work        : $work_size\n"
    info+=" backup      : $backup_size\n"
    info+=" home        : $home_size\n"
    info+=" union       : $union_size\n"
    info+=" swap        : $swap_size\n"
    IFS='#'; dialog_continue "$info"; IFS=$OLD_IFS
    zm_auto_select="yes"

    

    $SUDO rmmod nbd || true
    $SUDO modprobe nbd max_part=16
    ( echo "Createing qemu $format disk"

    local nbdindex=0
    if [ -n "$sfsroot_size" ];then
        init_qemu_disk $nbdindex $sfsroot_qcow2_file $sfsroot_size $orig_format
        $SUDO mkfs.ext4 -L sfsroot /dev/nbd${nbdindex}p1
        install_system /dev/nbd${nbdindex}p1
        install_grub disk=/dev/nbd0:root=/dev/nbd${nbdindex}p1
        update_grub_config /dev/nbd${nbdindex}p1
        uninit_qemu_disk $nbdindex
        convert_qemu_img $orig_format $disk_format $sfsroot_qcow2_file $sfsroot_file
        nbdindex=$(expr $nbdindex + 1)
    fi

    if [ -n "$home_size" ];then
        init_qemu_disk ${nbdindex} $home_qcow2_file $home_size $orig_format
        $SUDO mkfs.ext4 -L home /dev/nbd${nbdindex}p1
        if [ -e "$zm_dir/home.sfs" ];then
            mount_device -t tmpfs -o mode=755 tmpfs $unionfs_dir
            mount_unionfs home $zm_dir $sfs_mnt_dir $unionfs_dir $root_dir
            mount_device /dev/nbd${nbdindex}p1 $mnt_dir
            $SUDO rsync -a $root_dir/ $mnt_dir
            umount_device $mnt_dir
            umount_device $root_dir
            umount_device $unionfs_dir
            umount_all_sfs $sfs_mnt_dir home
        fi
        uninit_qemu_disk ${nbdindex}
        convert_qemu_img $orig_format $disk_format $home_qcow2_file $home_file
        nbdindex=$(expr $nbdindex + 1)
    fi

    if [ -n "$work_size" ];then
        init_qemu_disk ${nbdindex} $work_qcow2_file $work_size $orig_format
        $SUDO mkfs.ext4 -L work /dev/nbd${nbdindex}p1
        uninit_qemu_disk ${nbdindex}
        convert_qemu_img $orig_format $disk_format $work_qcow2_file $work_file
        nbdindex=$(expr $nbdindex + 1)
    fi

    if [ -n "$backup_size" ];then
        init_qemu_disk ${nbdindex} $backup_qcow2_file $backup_size $orig_format
        $SUDO mkfs.ext4 -L backup /dev/nbd${nbdindex}p1
        uninit_qemu_disk ${nbdindex}
        convert_qemu_img $orig_format $disk_format $backup_qcow2_file $backup_file
        nbdindex=$(expr $nbdindex + 1)
    fi

    if [ -n "$swap_size" ];then
        init_qemu_disk ${nbdindex} $swap_qcow2_file $swap_size $orig_format
        $SUDO mkswap -f -L swap /dev/nbd${nbdindex}p1
        uninit_qemu_disk ${nbdindex}
        convert_qemu_img $orig_format $disk_format $swap_qcow2_file $swap_file
        nbdindex=$(expr $nbdindex + 1)
    fi

    if [ -n "$union_size" ];then
        init_qemu_disk $nbdindex $union_qcow2_file $union_size $orig_format
        $SUDO mkfs.ext4 -L union /dev/nbd${nbdindex}p1
        uninit_qemu_disk $nbdindex 
        convert_qemu_img $orig_format $disk_format $union_qcow2_file $union_file
        nbdindex=$(expr $nbdindex + 1)
    fi 
    )
    sleep 5
    $SUDO rmmod nbd || true

    echo ""
    echo "kvm -m 1024 -hda $sfsroot_file"
    echo "kvm -m 2048 -hda $sfsroot_file -hdb $work_file"
    
    local params="-M q35 -m 2048"
    test -e $sfsroot_file && params+=" -drive file=$sfsroot_file,if=virtio"
    test -e $swap_file && params+=" -drive file=$swap_file,if=virtio"
    test -e $union_file && params+=" -drive file=$union_file,if=virtio"
    test -e $home_file && params+=" -drive file=$home_file,if=virtio"
    test -e $work_file && params+=" -drive file=$work_file,if=virtio"
    test -e $backup_file && params+=" -drive file=$backup_file,if=virtio"
    echo "kvm $params" > $installdir/kvm.sh
    chmod +x $installdir/kvm.sh
    echo ""
}

default_zm_setup()
{
    echo "change root password..."
    echo root:'root' | $SUDO chpasswd
    $SUDO systemctl disable cron.service || true
}

zm_set_hostname()
{
    local name="$1"

    if [ -z "$name" ];then
        name="$zm_user"
    fi

    $SUDO hostname $name
    echo "$name" | $SUDO tee /etc/hostname
    $SUDO sed -i "s/^127.0.0.1.*$/127.0.0.1	localhost.localdomain	localhost	 $name/g" /etc/hosts
    $SUDO sed -i "s/^::1.*$/::1	localhost.localdomain	localhost	 $name/g" /etc/hosts
}

auto_install()
{
    test -d /run/lock/ || mkdir -p /run/lock

    install_softs

    default_zm_setup
    zm_set_hostname 
    zm_user_command zm_setup

    gen_root_sfs / $zm_dir/${root_sfs_name}
    if [ ! -e "$zm_dir/${home_sfs_name}" ];then
        gen_home_sfs /home $zm_dir/${home_sfs_name}
    fi
    gen_kernel $zm_dir
    gen_initrd $zm_dir

    sync;sync;sync
}

make_debian_base_system()
{
    check_binary debootstrap

    if [ ! -e "$root_dir/bin/sh" ];then
        if [ -d $cache_dir/apt-archives ];then
            mkdir -p $root_dir/var/cache/apt/archives
            mount_device --bind $cache_dir/apt-archives $root_dir/var/cache/apt/archives
        fi
        local base_softs="aptitude,bash,rsync,dosfstools,initramfs-tools,systemd"
        debootstrap --include=$base_softs --arch=$zm_arch ${os_name#*.} $root_dir $src_url/debian
        if [ -d $cache_dir/apt-archives ];then
            umount_device $root_dir/var/cache/apt/archives
        fi
    fi
    sed -i "s/update_initramfs=yes/update_initramfs=no/g" $root_dir/etc/initramfs-tools/update-initramfs.conf
}

make_archlinux_base_system()
{
    root_dir=$build_dir/root.$zm_arch
    if [ ! -d "$root_dir" ];then
        md5sum_file=$dl_dir/md5sums.txt
        download_file $src_url/archlinux/iso/latest/md5sums.txt $md5sum_file
        bootstrap_file=$(cat $md5sum_file | grep $zm_arch | awk '{print $2}')
        bootstrap_md5=$(cat $md5sum_file | grep $zm_arch | awk '{print $1}')
        download_file $src_url/archlinux/iso/latest/$bootstrap_file /work/cache/pacman/pkg/$bootstrap_file $bootstrap_md5
        tar xvf /work/cache/pacman/pkg/$bootstrap_file -C $build_dir/
    fi

    echo "Server = $src_url/archlinux/\$repo/os/\$arch" > $root_dir/etc/pacman.d/mirrorlist
    sed -i "s/^#MAKEFLAGS=\"-j2\"/MAKEFLAGS=\"-j$cpu_counts\"/g" $root_dir/etc/makepkg.conf
    sed -i -e '/^#\[custom\]/,/^#Server = file:/s/\#//g' \
        -e 's#Server = file.*$#Server = file:///work/cache/pacman/repo#g' \
        -e 's@^#CacheDir.*/var/cache/pacman/pkg.*$@CacheDir = /work/cache/pacman/pkg/@g' \
        $root_dir/etc/pacman.conf
    # -e "s/#\[multilib\]/\[multilib\]/g" -e "/\[multilib\]/{n;s/#//g}" 

    if false;then
        if ! grep "archlinuxfr" $root_dir//etc/pacman.conf > /dev/null;then
            echo "" >> $root_dir//etc/pacman.conf
            echo "[archlinuxfr]" >> $root_dir//etc/pacman.conf
            echo "SigLevel = Optional TrustAll" >> $root_dir//etc/pacman.conf
            echo "Server = http://repo.archlinux.fr/\$arch" >> $root_dir//etc/pacman.conf
        fi
    fi
}

make_system()
{
    need_root
    print_env

    local cmd=""

    case "$os_name" in
        debian.*)
            make_debian_base_system
            ;;
        archlinux)
            make_archlinux_base_system
            cmd+="test -e /etc/pacman.d/gnupg/trustdb.gpg || { pacman-key --init; pacman-key --populate archlinux; };"
            local base_softs="file findutils gawk gettext grep gzip pacman sed sudo util-linux which"
            cmd+="pacman -Suy --noconfirm;pacman -S --needed --noconfirm $base_softs;"
            ;;
        *)
            return
            ;;
    esac

    # local zm_options=""
    # zm_options="--install-zm $root_dir/"
    # test -z "$zm_user" || zm_options+=" --zm-user $zm_user"
    # test -z "$zm_user_dir" || zm_options+=" --zm-userdir $zm_user_dir"
    # $zm_path $zm_options 

    install_zm $root_dir

    mkdir -p $zm_dir

    local zm_options=""
    zm_options+=" --zm-user $zm_user"
    zm_options+=" --zm-dir $zm_dir"
    zm_options+=" --os-name $os_name"
    zm_options+=" --arch $zm_arch"
    zm_options+=" --zm-user-passwd \"$zm_user_passwd\""
    zm_options+=" auto_install"
    if $zm_debug;then
        zm_options+=" --debug"
    fi
    cmd+="zm $zm_options"
    $SUDO systemctl stop virtualbox.service || true
    $SUDO systemctl stop nfs-common.service || true
    zm_chroot $root_dir $cmd
}

install_system()
{
    local dst=$1

    if [ -b $dst ];then
        mount_device $dst $root_dir
        dst_dir=$root_dir
    else
        dst_dir=$dst
    fi

    local install_dir
    install_dir=$dst_dir/linux/${zm_user}.${os_name}.${zm_arch}
    $SUDO mkdir -p $install_dir

    local root_sfs_file=$zm_dir/${root_sfs_name}
    local home_sfs_file=$zm_dir/${home_sfs_name}
    local kernel_file=$zm_dir/${kernel_name}
    local initrd_file=$zm_dir/${initrd_name}

    check_file_exist $root_sfs_file
    check_file_exist $kernel_file
    check_file_exist $initrd_file

    $SUDO $CP $kernel_file $install_dir
    $SUDO $CP $initrd_file $install_dir

    $SUDO rsync -avP $root_sfs_file $install_dir
    $SUDO rsync -avP $root_sfs_file.[0-9] $install_dir || true
    if [ -e $home_sfs_file ];then
        $SUDO rsync -avP --no-l -L $home_sfs_file $install_dir
        $SUDO rsync -avP --no-l -L $home_sfs_file.[0-9] $install_dir || true
    fi

    $zm_path --install-zm $root_dir

    sync;sync;sync

    # md5sum $temp_dir/${root_sfs_name}
    # md5sum $temp_dir/${initrd_name}

    if [ -b $dst ];then
        umount_device $dst_dir
    fi
}

add_soft()
{	
    echo "$@" | $SUDO tee -a $soft_list_file > /dev/null
}

add_aur_soft()
{
    archlinux_aur_softs+=" $@"
}

debian_apt_update()
{
    if [ ! -e /tmp/.zm_debian_apt_update ];then
        if which nc > /dev/null;then
            echo "Testing internet connect status..."
            # nc -v -w 3 www.baidu.com -z 80
            nc -w 3 www.baidu.com -z 80
        fi

        echo "update system..."
        $APTGET update
        touch /tmp/.zm_debian_apt_update
    fi
}

default_zm_setup_apt()
{
    local deb_ver=${os_name#*.}
    temp_apt_src_file=$build_dir/sources.list
    > $temp_apt_src_file
    echo "deb $src_url/debian $deb_ver main non-free contrib" >> $temp_apt_src_file
    echo "deb-src $src_url/debian $deb_ver main non-free contrib" >> $temp_apt_src_file
    if [ "$deb_ver" != "unstable" ];then
        echo "deb $src_url/debian $deb_ver-updates main non-free contrib" >> $temp_apt_src_file
        echo "deb $src_url/debian $deb_ver-backports main non-free contrib" >> $temp_apt_src_file
        echo "deb $src_url/debian-security/ $deb_ver/updates main non-free contrib" >> $temp_apt_src_file
        echo "deb-src $src_url/debian $deb_ver-updates main non-free contrib" >> $temp_apt_src_file
        echo "deb-src $src_url/debian $deb_ver-backports main non-free contrib" >> $temp_apt_src_file
        echo "deb-src $src_url/debian-security/ $deb_ver/updates main non-free contrib" >> $temp_apt_src_file
    fi
    if ! diff $temp_apt_src_file /etc/apt/sources.list > /dev/null 2>&1;then
        mv $temp_apt_src_file /etc/apt/sources.list
    fi
}

add_debian_base_softs()
{
    case "$zm_arch" in
        amd64)
            kernel_softs="linux-image-amd64 linux-headers-amd64"; 
            ;;
        *)
            kernel_softs="linux-image-686-pae linux-headers-686-pae"; 
            ;;
    esac

    case "$os_name" in
        debian.unstable)
            add_soft systemd-container
            ;;
    esac

    add_soft "$kernel_softs"
    add_soft locales 
    add_soft bash bash-completion
    add_soft grub2 syslinux udev procinfo 
    add_soft aptitude lsof
    add_soft acpi-support acpi
    add_soft hdparm usbutils lshw 
    add_soft module-init-tools dosfstools exfat-utils ntfs-3g busybox mbr 
    add_soft iproute realpath rsync util-linux gpart debootstrap pciutils 
    add_soft systemd
    add_soft e2fsprogs
    add_soft lftp less bc
    add_soft ssh openssh-server nfs-common 
    add_soft vim gzip xz-utils 
    add_soft netcat network-manager 
    add_soft dh-make
    add_soft qemu-guest-agent
    add_soft apt-file
    # pigz lzip plzip pxz p7zip-full

    add_soft parted lvm2 initramfs-tools lsb-release genisoimage
    add_soft squashfs-tools aufs-tools 
}


download_debian_softs()
{
    add_debian_base_softs
    zm_user_command zm_add_softs
    zm_user_command zm_setup_apt
    debian_apt_update

    _download_soft()
    {
        test "$1" = "-f" && shift
        echo ">>>> download $@"
        $APTGET install -d -m $@
    }
    if $zm_debug;then
        cat $soft_list_file | while read line; do _download_soft $line; done
    else
        _download_soft $(cat $soft_list_file)
    fi
}

download_archlinux_softs()
{
    echo ""
}

install_debian_soft()
{
    debian_apt_update

    param=$@
    test "$1" = "-qq" && shift
    if ! is_installed $@;then
        echo ">>>> install $@"
        $APTGET install $param
    fi
}

install_archlinux_soft()
{
    # pacman -Suy --noconfirm
    $SUDO pacman -S --force --needed --noconfirm $@
}

install_soft()
{
    case "$os_name" in
        debian.*)
            install_debian_soft $@
            ;;
        archlinux)
            install_archlinux_soft $@
            ;;
        *)
            return
            ;;
    esac
}

install_debian_softs()
{
    print_env

    # export DEBIAN_FRONTEND=gnome
    export DEBIAN_FRONTEND=noninteractive

    if [ $zm_arch = "amd64" ];then
        $SUDO dpkg --add-architecture i386
    fi

    download_softs

    $APTGET upgrade || true

    cat $soft_list_file | while read line; do install_soft -qq $line; done

    $SUDO $APTGET autoremove || true
}

install_archlinux_softs()
{
    add_soft base-devel
    add_soft autoconf automake binutils
    add_soft bison fakeroot flex libtool groff
    add_soft make m4 patch pkg-config texinfo
    # add_soft gcc-multilib gcc-libs-multilib
    # add_soft multilib-devel
    add_soft systemd-sysvcompat 
    add_soft openssh wget dosfstools 
    add_soft linux man
    add_soft squashfs-tools 
    add_soft lvm2
    add_soft net-tools
    add_soft yaourt
    # add_soft 
    # add_soft 
    zm_user_command zm_add_softs
    if [ ! -e /tmp/.pacman.update ];then
        $SUDO pacman -Suy --force --noconfirm
        $SUDO pacman -S --force --needed --noconfirm archlinux-keyring
        touch /tmp/.pacman.update
    fi
    $SUDO pacman -S --force --needed --noconfirm $(cat $soft_list_file)

    if [ -n "$archlinux_aur_softs" -a "$(id -u)" != "0" ];then
        repo_dir=/work/cache/pacman/repo
        $SUDO touch ${repo_dir}/custom.db
        yaourt -Syu

        for soft in $archlinux_aur_softs
        do
            if ! pacman -Q $soft > /dev/null 2>&1;then
                yaourt -S --noconfirm $soft
            fi
        done
    fi
}

download_softs()
{
    $SUDO rm -f $soft_list_file
    case "$os_name" in
        debian.*)
            download_debian_softs
            ;;
        archlinux)
            download_archlinux_softs
            ;;
        *)
            return
            ;;
    esac
}

install_softs()
{
    $SUDO rm -f $soft_list_file
    case "$os_name" in
        debian.*)
            install_debian_softs
            ;;
        archlinux)
            install_archlinux_softs
            ;;
        *)
            return
            ;;
    esac
}

dump_depends()
{
    softs=$(cat $soft_list_file)
    for deb in $softs
    do
        apt-cache -i depends $deb
        # apt-cache --no-suggests depends $deb
    done
    $SUDO apt-get install apt-rdepends
    apt-rdepends -d $softs > /tmp/dep.dot
    cat /tmp/dep.dot | less
    rm /tmp/dep.dot
}

zm_user_command()
{
    cmd=$1
    shift
    test -z "$cmd" && return
    if command -v $cmd > /dev/null 2>&1;then
        $cmd $*
        return
    fi
}

zm_add_groups()
{
    groups="$@"
    for g in $groups
    do
        if ( grep $g /etc/group > /dev/null; ) && ! ( groups $zm_user | grep $g > /dev/null; );then
            $SUDO usermod -a -G $g $zm_user
        fi
    done
}

zm_setup_user()
{
    local userid=8888

    if ! grep $userid /etc/group > /dev/null;then
        $SUDO groupadd -g $userid $zm_user 
        $SUDO useradd -m -s /bin/bash -u $userid -g $userid $zm_user
        # useradd -m -s /bin/zsh -u $userid -g $userid $zm_user
        zm_add_groups floppy dialout audio video plugdev netdev sudo kvm vboxusers uucp docker disk
        # echo "
        # $zm_user    ALL=NOPASSWD: /bin/mount
        # $zm_user    ALL=NOPASSWD: /bin/umount
        # " > /etc/sudoers.d/$zm_user.mount
    fi
    echo "$zm_user	ALL=(ALL:ALL) ALL" | $SUDO tee /etc/sudoers.d/$zm_user
    if [ -d "$work_mountdir" ];then
        $SUDO chown $zm_user:$zm_user $work_mountdir #-R   
    fi
    if [ -d "$HOME" ];then
        $SUDO chown $zm_user:$zm_user $HOME #-R
        # chown $zm_user:$zm_user $(cat /etc/passwd | grep $zm_user | awk -F: '{print $6}') #-R
    fi
    echo $zm_user:"$zm_user_passwd" | $SUDO chpasswd
    echo root:"$zm_root_passwd" | $SUDO chpasswd
}

install_zm()
{
    local _root_dir=$1
    if [ -z "$_root_dir" ];then
        _root_dir=""
    fi

    zm_copy_source $_root_dir/usr/share/zm

    local _user_dir=$(readlink -m $_root_dir/usr/share/zm/user/$zm_user)
    if [ "$zm_user_dir" != "$_user_dir" -a -d "$zm_user_dir" ];then
        rm_safe $_user_dir
        $SUDO mkdir -p $_user_dir
        $SUDO $CP $zm_user_dir/. $_user_dir/
    fi

    case "$os_name" in
        debian.*)
            local initramfs_dir=usr/share/initramfs-tools
            $SUDO install -D $zm_workdir/common/sfsroot.initramfs.hooks $_root_dir/$initramfs_dir/hooks/sfsroot
            $SUDO install -D $zm_workdir/common/sfsroot.boot $_root_dir/$initramfs_dir/scripts/local-premount/sfsroot
            ;;
        archlinux)
            local initcpio_dir=etc/initcpio
            $SUDO install -D $zm_workdir/common/sfsroot.boot $_root_dir/$initcpio_dir/hooks/sfsroot
            $SUDO install -D $zm_workdir/common/sfsroot.initcpio.install $_root_dir/$initcpio_dir/install/sfsroot
            ;;
        *)
            ;;
    esac

    $SUDO mkdir -p $_root_dir/usr/bin
    $SUDO ln -svf /usr/share/zm/zm $_root_dir/usr/bin/zm

    # mkdir -p $_root_dir/usr/share/bash-completion/completions
    # ln -svf /usr/share/zm/common/complete.bash $_root_dir/usr/share/bash-completion/completions/zm
    $SUDO mkdir -p $_root_dir/etc/bash_completion.d/
    $SUDO ln -svf /usr/share/zm/common/complete.bash $_root_dir/etc/bash_completion.d/zm

    echo "install zm to $_root_dir/"
}

uninstall_zm()
{
    local _root_dir=$1
    if [ -z "$_root_dir" ];then
        _root_dir=""
    fi
    rm_safe $_root_dir/usr/bin/zm
    rm_safe $_root_dir/usr/share/zm
    rm_safe $_root_dir/etc/bash_completion.d/zm
}

run_zm_debconf()
{
    run_file=$1
    chmod +x $run_file
    $run_file
}

zm_setup_tzdata() 
{
    area=$1
    zone=$2
    case "$os_name" in
        debian.*)
            echo "#!/bin/bash
            . $zm_workdir/common/init_debconf.sh

            rm -f /etc/localtime
            rm -f /etc/timezone
            db_set tzdata/Areas '$area' || true
            db_set tzdata/Zones/$area '$zone' || true
            db_fset tzdata/Areas seen true || true
            db_fset tzdata/Zones/$area seen true || true

            " > $build_dir/zm_debconf
            run_zm_debconf $build_dir/zm_debconf
            DEBIAN_FRONTEND=noninteractive dpkg-reconfigure -u tzdata || true
            ;;
        archlinux)
            $SUDO timedatectl set-timezone $area/$zone
            ;;
        *)
            ;;
    esac
}

zm_setup_sh()
{
    default_shell=$1

    case "$default_shell" in
        bash) 
            echo "#!/bin/bash
            . $zm_workdir/common/init_debconf.sh
            db_set dash/sh 'false' || true
            db_fset dash/sh seen true || true
            " > $build_dir/zm_debconf
            ;;
        dash)
            echo "#!/bin/bash
            . $zm_workdir/common/init_debconf.sh
            db_set dash/sh 'true' || true
            db_fset dash/sh seen true || true
            " > $build_dir/zm_debconf
            ;;
        *)
            return
            ;;
    esac

    run_zm_debconf $build_dir/zm_debconf
    dpkg-reconfigure -u dash || true
}

zm_setup_default_locales()
{
    rm -f /etc/locale.gen

    echo "#!/bin/bash
    . $zm_workdir/common/init_debconf.sh

    db_set locales/locales_to_be_generated 'en_US.UTF-8 UTF-8, zh_CN.GBK GBK, zh_CN.UTF-8 UTF-8'
    db_subst locales/default_environment_locale locales 'en_US.UTF-8, zh_CN.GBK, zh_CN.UTF-8'
    db_set locales/default_environment_locale 'en_US.UTF-8'
    db_fset locales/default_environment_locale seen true
    db_fset locales/locales_to_be_generated seen true

    " > $build_dir/zm_debconf
    run_zm_debconf $build_dir/zm_debconf
    dpkg-reconfigure -u locales || true
}

zm_mount_backup()
{
    if [ -z "$@" ];then
        return
    fi

    bak_name="$1"
    bak_root_dir=$backup_workdir/$bak_name
    bak_unionfs_dir=$backup_mountdir/unionfs/$bak_name

    test -e $backup_mountdir/${bak_name}.sfs || return 1

    if ! readlink -e $bak_root_dir > /dev/null 2>&1;then
        $SUDO rmdir $bak_root_dir > /dev/null 2>&1 || true
    fi
    if ! is_mounted $bak_root_dir;then
        $SUDO mkdir -m 700 -p $bak_root_dir
        $SUDO mkdir -m 700 -p $bak_unionfs_dir

        # __mount_with_unionfs $bak_name $backup_mountdir $sfs_mpath $bak_unionfs_dir $bak_root_dir
        mount_unionfs $bak_name $backup_mountdir $sfs_mpath $bak_unionfs_dir $bak_root_dir
        if [ $? != 0 ];then
            echo "mount unionfs error"
            $SUDO rmdir $bak_root_dir
            return 1
        fi

        $SUDO chown $UID $bak_unionfs_dir
        $SUDO chown $UID $bak_root_dir
    fi

    if [ -L $work_mountdir/$bak_name ];then
        bak_work_dir=$(readlink -e $work_mountdir/$bak_name)
        if [ "$bak_work_dir" = "$bak_root_dir" ];then
            echo $work_mountdir/$bak_name 
            return 0
        else
            rm -vf $work_mountdir/$bak_name
        fi
    fi

    if [ -e $work_mountdir/$bak_name ];then
        echo $bak_root_dir
        return 0
    fi

    ln -s $bak_root_dir $work_mountdir/$bak_name 
    echo $work_mountdir/$bak_name 
}

get_backup_unionfs_dir()
{
    local bak_name="$1"

    case $(support_union_fs) in
        overlay)
            echo "$backup_mountdir/unionfs/${bak_name}/upper"
            ;;
        aufs)
            echo "$backup_mountdir/unionfs/${bak_name}/aufs"
            ;;
        *)
            err "no support union filesystem."
            ;;
    esac
}

zm_backup_branch()
{
    local bak_name="$1"
    local sfsfile=$backup_mountdir/${bak_name}.sfs

    local bak_path=$(get_backup_unionfs_dir "$bak_name")

    branch=$(get_sfs_new_branch_num $sfsfile)
    gen_common_sfs $bak_path ${sfsfile}.${branch}
}

show_backup_info()
{
    for bak_name in $@;do
        local all_sfs=$(get_all_sfs $backup_mountdir/${bak_name}.sfs)
        if [ ! -z "$all_sfs" ];then
            ls -ltGch $all_sfs
        fi
        # find $backup_mountdir -maxdepth 1 -name ${bak_name}.sfs -printf "%P %c\n" 
    done
}

zm_backup_info()
{
    if [ $# -eq 0 ];then
        show_backup_info $(basename -s .sfs -a $(cd $backup_mountdir;/bin/ls *.sfs 2> /dev/null)) | less
    else
        show_backup_info $@
    fi
}

zm_backup_dir()
{
    local bak_dir="$1"
    local bak_name="$2"

    test -e "$bak_dir" || return 0

    local bak_path=$(readlink -e $bak_dir)
    if [ -z "$bak_name" ];then
        bak_name=$(basename $bak_path)
    fi

    local bak_sfs=$backup_mountdir/${bak_name}.sfs

    test -e "$bak_sfs" && dialog_continue "$bak_sfs is exist, continue?"
    local all_sfs=$(get_all_sfs $bak_sfs)
    if [ -n "$all_sfs" ];then
        $SUDO chattr -i $all_sfs
    fi
    gen_common_sfs $bak_path $bak_sfs
    # $SUDO chattr +i $bak_sfs
}


zm_check_backup()
{
    bak_params=$@
    if [ -z "$@" ];then
        bak_params='.'
    fi

    $SUDO mount | grep $sfs_mpath/$bak_name > /dev/null 2>&1 || return 1
    $SUDO mount | grep $unionfs_dir_dir/$bak_name > /dev/null 2>&1 || return 1
    # echo $bak_root_dir | grep $(readlink -e $PWD) > /dev/null && cd
    return 0
}

zm_remove_backup()
{
    local backup_unionfs_dir
    for bak_name in $@
    do
        bak_sfs=$backup_mountdir/${bak_name}.sfs

        if [ ! -e $bak_sfs ];then
            return 1
        fi

        local all_sfs=$(get_all_sfs $bak_sfs)
        if [ -n "$all_sfs" ];then
            $SUDO chattr -i $all_sfs
            backup_file $all_sfs
        fi
        backup_unionfs_dir=$backup_mountdir/unionfs/${bak_name}
        if [ -d $bakcup_unionfs_dir ];then
            $SUDO rm -rf "$backup_unionfs_dir"
        fi
    done
}

zm_sync_backup()
{
    local bakname="$1"
    local syncdir="$2"

    if [ -z "$bakname" ];then
        err "param error, bakname is blank."
    fi

    if [ -z "$syncdir" ];then
        syncdir="$zm_curdir"
    fi

    local sfsfile=$backup_mountdir/${bakname}.sfs
    if [ -e "$sfsfile" ];then
        if [ -e $syncdir/${bakname}.sfs ];then
            backup_file $syncdir/${bakname}.sfs*
        fi
        $SUDO rsync -avP ${sfsfile}* $syncdir/
    fi

    local bakpath=$(get_backup_unionfs_dir "$bakname")
    local dstpath=$syncdir/unionfs/$bakname
    if [ -d "$dstpath" ];then
        backup_file "$dstpath"
    fi

    if [ -d "$bakpath" ];then
        if [ "$(ls -A $bakpath)" != "" ];then
            mkdir -p $syncdir/unionfs
            $SUDO rsync -avP --delete $bakpath/ $dstpath
        fi
    fi
}

zm_revert_backup()
{
    local bakname="$1"
    local syncdir="$2"
}

zm_syncdir_backup()
{
    local bakname="$1"
    local syncdir="$2"

    if [ -z "$bakname" ];then
        err "param error, bakname is blank."
    fi

    if [ -z "$syncdir" ];then
        syncdir="$work_mountdir/$bakname"
    fi

    if [ -e "$syncdir" ];then
        err "<$FUNCNAME> $syncdir is exist."
    fi

    mkdir -p $syncdir
    local mountdir="$(zm_mount_backup $bakname)"
    if [ -n "$mountdir" ];then
        $SUDO rsync -avP $mountdir/ $syncdir
    else
        rmdir $syncdir
        err "sync backup to $syncdir error."
    fi
}

zm_remove_backup_branch()
{
    local bak_name=$1
    local del_branch=$2

    bak_sfs=$backup_mountdir/${bak_name}.sfs

    if [ ! -e $bak_sfs -o -z "$del_branch" ];then
        err "$FUNCNAME param error"
    fi

    local cur_branch=$(get_sfs_cur_branch_num $bak_sfs)
    start_branch=$(expr $cur_branch - $del_branch + 1)

    if [ $start_branch -lt 0 -o $start_branch -gt $(expr $cur_branch + 1) ];then
        err "$FUNCNAME branch param error."
    fi

    local backup_unionfs_dir=$(get_backup_unionfs_dir $bak_name)
    if [ -d "$backup_unionfs_dir" ];then
        $SUDO rm -rf "$backup_unionfs_dir"
    fi

    for branch in $(seq $start_branch $cur_branch);do
        local sfs=$backup_mountdir/${bak_name}.sfs.${branch}
        $SUDO chattr -i $sfs
        backup_file $sfs
    done
}

zm_umount_backup()
{
    bak_params=$1
    if [ -z "$1" ];then
        bak_params='.'
    fi

    is_mounted $backup_mountdir || return

    if [ ! -e $backup_workdir ];then
        echo "zm backup dir is not exist."
        return 1
    fi

    for bak_name in $bak_params
    do

        if [ -z $bak_name -o "$bak_name" = '..' ];then
            echo "param error."
            return 0
        fi

        if [ $bak_name = '.' ];then
            bak_name=$(basename $(readlink -e $bak_name))
        fi

        umount_storage $backup_workdir/$bak_name 

        bak_zm_sfs_dir=$sfs_mpath/$bak_name
        for branch in $(seq 1 $unionfs_max_branch)
        do
            branch_sfs_mount_dir=${bak_zm_sfs_dir}.${branch}
            if [ -d "$branch_sfs_mount_dir" ];then
                umount_storage $branch_sfs_mount_dir
                continue
            fi
            break
        done  

        umount_storage $bak_zm_sfs_dir 

        bak_work_dir=$(readlink -f $work_mountdir/$bak_name)
        if [ "$bak_work_dir" = "$backup_workdir/$bak_name" ];then
            rm -vf $work_mountdir/$bak_name
        fi

        local bak_unionfs_dir=$backup_mountdir/unionfs/$bak_name
        for dir in upper aufs work/work work " ";do
            if [ -d $bak_unionfs_dir/$dir ];then
                if [ -e "$bak_unionfs_dir/$dir/$unionfs_clean_file" ];then
                    $SUDO rm -rf $bak_unionfs_dir/$dir/{*,.[!.]*,..?*}
                fi
                $SUDO rmdir --ignore-fail-on-non-empty $bak_unionfs_dir/$dir
            fi
        done

    done
}



test_stable()
{
    echo "test"    
}

zm_argv_del()
{
    if [ -n "$1" ];then
        # zm_argv=${zm_argv/"$1"}
        zm_argv=$(echo " "$zm_argv" " | sed -e "s# $1 # #g" | xargs)
    fi
}

zm_config()
{

    while [ $# -gt 0 ]; do
        case "$1" in
            --root-sfs) zm_argv_del $1;shift; root_sfs_name=$1; zm_argv_del $1;shift; 
                ;;
            --home-sfs) zm_argv_del $1;shift; home_sfs_name=$1; zm_argv_del $1;shift; 
                ;;
            --initrd) zm_argv_del $1;shift; initrd_name=$1; zm_argv_del $1;shift; 
                ;;
            --kernel-name) zm_argv_del $1;shift; kernel_name=$1; zm_argv_del $1;shift; 
                ;;
            --kernel-ver) zm_argv_del $1;shift; kernel_ver=$1; zm_argv_del $1;shift; 
                ;;
            --zm-user) zm_argv_del $1;shift; zm_user=$1; zm_argv_del $1;shift;
                ;;
            --zm-user-passwd) zm_argv_del $1;shift; zm_user_passwd=$1; zm_argv_del $1;shift;
                ;;
            --zm-root-passwd) zm_argv_del $1;shift; zm_root_passwd=$1; zm_argv_del $1;shift;
                ;;
            --zm-userdir) zm_argv_del $1;shift; zm_user_dir=$1; zm_argv_del $1;shift;
                ;;
            --zm-dir) zm_argv_del $1;shift; zm_dir=$1; zm_argv_del $1;shift;
                ;;
            --arch) zm_argv_del $1;shift; zm_arch=$1; zm_argv_del $1;shift;
                ;;
            --src-url) zm_argv_del $1;shift; src_url="$1"; zm_argv_del "$1";shift;
                ;;
            --os-name) zm_argv_del $1;shift; os_name=$1; zm_argv_del $1;shift;
                ;;
            --yes|-y) zm_argv_del $1;shift; zm_auto_select="yes";
                ;;
            --build-dir) zm_argv_del $1;shift; build_dir=$1; zm_argv_del $1;shift;
                ;;
            --keep-build|-k) zm_argv_del $1;shift; keep_build_dir="yes";
                ;;
            --kernel-params) zm_argv_del $1;shift; kernel_params="$1"; zm_argv_del $1;shift;
                ;;
            --debug) zm_argv_del $1;shift; zm_debug=true;
                ;;
            *) shift; 
                ;;
        esac
    done

    if [ -z "$zm_arch" ];then
        case "$os_name" in
            debian.*)
                # zm_arch=$(dpkg --print-architecture)
                zm_arch="amd64"
                ;;
            archlinux)
                zm_arch="x86_64"
                ;;
            *)
                err "zm_arch=$zm_arch error"
                ;;
        esac
    fi

    if $zm_debug;then
        set -x
        CP="/bin/cp -rafv"
    fi

    if [ -z "$build_dir" ];then
        if $zm_debug;then
            build_dir=/tmp/zm_build
            mkdir -p $build_dir
        else
            #can use TMPDIR change, see man mktemp
            build_dir=$(mktemp -d)
        fi
    fi

    test -d "$build_dir" || err "build_dir ($build_dir) isn't dir."
    if [ $(get_usedsize_dir_mb "$build_dir") -gt 5000 ];then
        dialog_continue "build dir($build_dir) have more data, please check again."
    fi

    mnt_dir=$build_dir/mnt
    iso_dir=$build_dir/iso
    sfs_mnt_dir=$build_dir/sfs
    unionfs_dir=$build_dir/unionfs
    root_dir=$build_dir/root
    dl_dir=$build_dir/dl

    soft_list_file=$build_dir/soft_list

    mkdir -p $mnt_dir
    mkdir -p $iso_dir
    mkdir -p $sfs_mnt_dir
    mkdir -p $unionfs_dir
    mkdir -p $root_dir
    mkdir -p $dl_dir

    #trap zm_uninit ERR INT QUIT TERM EXIT
    trap zm_uninit ERR INT TERM EXIT

    if [ -z "$zm_user" ];then
        if [ -z "$SUDO_USER" ];then
            zm_user="$USER"
        else
            zm_user=$SUDO_USER
        fi
    fi
    test -z "$zm_user_passwd" && zm_user_passwd="$zm_user"
    test -z "$zm_root_passwd" && zm_root_passwd="$zm_user_passwd"

    if [ -z "$zm_dir" ];then
        zm_dir=$sfs_part_mpath/linux/${zm_user}.${os_name}.${zm_arch}
    fi

    if [ -z "$zm_user_dir" ];then
        zm_user_home=$(eval echo ~$zm_user)
        if [ -d $zm_user_home/.zm ];then
            zm_user_dir=$zm_user_home/.zm
        else
            if [ -d $zm_workdir/user/$zm_user ];then
                zm_user_dir=$zm_workdir/user/$zm_user
            else
                zm_user_dir=""
            fi
        fi
    fi
    if [ -n "$zm_user_dir" ];then
        check_file_exist "$zm_user_dir"
        zm_user_dir=$(readlink -f "$zm_user_dir")
    fi

    if [ -z "$kernel_params" ];then
        if $zm_debug;then
            kernel_params="debug"
        else
            kernel_params="quiet"
        fi
    fi

    if [ -e "$zm_user_dir/${os_name}.config.sh" ];then
        source $zm_user_dir/${os_name}.config.sh
    fi
    zm_user_command zm_user_init
}

zm_excute()
{
    while [ $# -gt 0 ]; do
        case "$1" in
            --zm-user-init) zm_argv_del $1;shift; sh $zm_user_dir/init;
                ;;
            --set-hostname) zm_set_hostname $1
                ;;
            --install-grub) shift; install_grub $@; exit 0; 
                ;;
            --update-grub-config) shift; update_grub_config $1; exit 0; 
                ;;
            --install-system) shift; install_system $1; test -z "$1" || shift; 
                ;;
            --make-system) shift; make_system; exit 0;
                ;;
            --make-mini-system) shift; $zm_path --zm-user mini --make-system; exit 0;
                ;;
            --gen-kernel) shift; gen_kernel $1; test -z "$1" || shift; 
                ;;
            --gen-initrd) shift; gen_initrd $1; test -z "$1" || shift; 
                ;;
            --gen-root) shift; gen_root_sfs / $1/${root_sfs_name}; test -z "$1" || shift; 
                ;;
            --gen-home) shift; gen_home_sfs /home $1/${home_sfs_name}; test -z "$1" || shift; 
                ;;
            --branch-system|-b) shift; branch_system $1; test -z "$1" || shift; 
                ;;
            --gen-iso-system) shift; gen_iso_system $1; test -z "$1" || shift; 
                ;;
            --gen-usb-system) shift; gen_usb_system $1; test -z "$1" || shift; 
                ;;
            --gen-usb-boot) shift; gen_usb_boot $1; test -z "$1" || shift;
                ;;
            --download-softs) download_softs; shift;
                ;;
            --install-soft|-i) shift; install_soft $@; exit 0; shift
                ;;
            --install-softs) install_softs; shift
                ;;
            --debconf-softs) zm_user_command debconf_softs; shift 
                ;;
            --setup) zm_user_command zm_setup; shift
                ;;
            --update-system|-u) shift; update_system $@; test -z "$1" || shift; 
                ;;
            --install-zm) shift; install_zm $1; test -z "$1" || shift
                ;;
            --uninstall-zm) shift; uninstall_zm $1; test -z "$1" || shift
                ;;
            --edit|-e) $EDITOR $zm_path; exit 0;
                ;;
            --chroot) shift; zm_chroot $@; exit 0;
                ;;
            --build-deb) shift; zm_build_deb_checkinstall $1; exit 0;
                ;;
            --backup-dir) shift; zm_backup_dir $@; exit 0;
                ;;
            --backup-branch) shift; zm_backup_branch $1; exit 0;
                ;;
            --remove-backup-branch) shift; zm_remove_backup_branch $@; exit 0;
                ;;
            --mount-backup) shift; zm_mount_backup $@; exit 0;
                ;;
            --umount-backup) shift; zm_umount_backup $@; exit 0;
                ;;
            --remove-backup) shift; zm_remove_backup $@; exit 0;
                ;;
            --syncdir-backup) shift; zm_syncdir_backup $@; exit 0;
                ;;
            --sync-backup) shift; zm_sync_backup $@; exit 0;
                ;;
            --revert-backup) shift; zm_revert_backup $@; exit 0;
                ;;
            --backup-info) shift; zm_backup_info $@; exit 0;
                ;;
            --create-part) shift; init_lvm_partition $@; exit 0;
                ;;
            --make-virtual-system) shift; make_virtual_system $@; exit 0;
                ;;
            --verbose) shift; print_env; exit 0;
                ;;
            --version)
                show_version; exit 0;
                ;;
            *) 
                if type -t "$1" > /dev/null;then
                    func=$1
                    shift
                    echo "run $func"
                    $func $@
                else
                    echo "unsupport param $1"
                    shift
                fi
                ;;
        esac
    done
}

get_env_vars()
{
    if [ -d "$BACKUP_MOUNTDIR" ];then
        backup_mountdir=$BACKUP_MOUNTDIR
    fi
}

zm_init()
{
    get_env_vars

    CP="/bin/cp -raf"

    # aptitude -d -R -y install 
    # APTGET='apt-get --yes'
    # APTGET='apt-get --no-install-recommends --no-install-suggests --yes'
    APTGET="$SUDO apt-get --yes 
    --no-install-recommends 
    --no-install-suggests 
    -o Dpkg::Options::=--force-confdef 
    -o Dpkg::Options::=--force-confnew
    "
    if [ -d $cache_dir/apt-archives ];then
        APTGET+=" -o Dir::Cache::Archives=$cache_dir/apt-archives"
    fi

    cpu_counts=`cat /proc/cpuinfo | grep "processor" | wc -l`

    GZ=gzip
    LZ=lzip
    XZ=xz

    which pigz > /dev/null 2>&1 && GZ="pigz -p $cpu_counts"
    which plzip > /dev/null 2>&1 && LZ="plzip -n $cpu_counts"
    which pxz > /dev/null 2>&1 && XZ="pxz -T$cpu_counts"

    root_sfs_name="root.sfs"
    home_sfs_name="home.sfs"
    initrd_name="initrd.img"
    kernel_name="vmlinuz"
    kernel_ver=""
    kernel_params=""

    os_name=$(show_os_name)

    zm_dir=""
    zm_user=""
    zm_user_dir=""

    build_dir=""
    keep_build_dir="no"

    # no multimedia
    src_url=http://mirrors.tuna.tsinghua.edu.cn
    # src_url=http://mirrors.163.com
    # src_url=http://mirrors.aliyun.com
    # src_url=http://mirrors.sohu.com
    # src_url=http://mirror.bit.edu.cn

    # have multimedia
    # src_url=http://free.nchc.org.tw
    # src_url=http://mirrors.ustc.edu.cn
    # src_url=http://ftp.hk.debian.org
    # src_url=http://mirrors.xmu.edu.cn
    # src_url=http://mirror.bjtu.edu.cn

    zm_auto_select="no"

    code_kernel_3_18=$(get_linux_kernel_code 3.18.0)
    KERNEL_VERSION=$(uname -r | awk  -F '-' 'BEGIN{OFS="."}{print $1}' | awk  -F '.' 'BEGIN{OFS="."}{print $1,$2,$3}')
    code_kernel_now=$(get_linux_kernel_code $KERNEL_VERSION)
    #echo $code_kernel_3_18 $code_kernel_now

    case $(support_union_fs) in
        overlay)
            unionfs_root_mpath=$union_mpath/root/upper
            unionfs_home_mpath=$union_mpath/home/upper
            ;;
        aufs)
            unionfs_root_mpath=$union_mpath/root
            unionfs_home_mpath=$union_mpath/home
            ;;
    esac

}

umount_all_device()
{
    if [ -e $build_dir/.mount_devs ];then
        local devs="$(cat $build_dir/.mount_devs)"
        for dev in $devs;do
            if $zm_debug;then
                echo "umount $dev..."
            fi
            umount_device $dev || true
        done
        rm -rf $build_dir/.mount_devs
    fi
}

zm_uninit()
{
    if $zm_debug;then
        set +x
    fi
    trap - ERR INT TERM EXIT
    if [ -d "$build_dir" ];then
        if $zm_debug;then
            echo "$zm_path $zm_argv Clean ..."
        fi
        umount_all_device
        if $zm_debug;then
            echo "In debug mode, please manual delete $build_dir"
        else
            if [ "$keep_build_dir" != "yes" ];then
                if [ $(get_usedsize_dir_mb "$build_dir") -gt 5000 ];then
                    dialog_continue "build dir($build_dir) have more data, please check again."
                fi
                rm -rf $build_dir 
            fi
        fi
    fi
}

zm_info()
{
    case "$1" in
        --print-workdir)
            echo $zm_workdir
            exit 0
            ;;
        --print-backup-workdir)
            echo $backup_workdir
            exit 0
            ;;
        --print-backup-mountdir)
            echo $backup_mountdir
            exit 0
            ;;
        --print-os-name)
            # lsb_release -i -s
            echo $os_name
            exit 0
            ;;
        --print-max-branch)
            echo $unionfs_max_branch
            exit 0
            ;;
        *)
            ;;
    esac
}

 
###############################################################################
###############################################################################
###############################################################################

if [ $# -eq 0 -o "$1" = "--help" ];then
    usage
    exit 0
fi

zm_argv=$@
zm_clean=true
zm_debug=false

export zm_debug

zm_init
zm_info $zm_argv
zm_config $zm_argv
zm_excute $zm_argv
zm_uninit


